module ActiveRecord
  module Validations
    module ClassMethods

#------------------------------------------------------------

CPF_REGEX  = /^\d{3}\.\d{3}\.\d{3}-\d{2}$/
CNPJ_REGEX = /^\d{2}\.\d{3}\.\d{3}\/\d{4}-\d{2}$/

#------------------------------------------------------------

def validates_cpf_of(*attr_names)
  configuration = DEFAULT_VALIDATION_OPTIONS.merge(attr_names.extract_options!)

  validates_each(attr_names, configuration) do |record, attr_name, value|
    unless cpf_valid?(value)
      record.errors.add(attr_name)
    end
  end
end

def validates_cnpj_of(*attr_names)
  configuration = DEFAULT_VALIDATION_OPTIONS.merge(attr_names.extract_options!)

  validates_each(attr_names, configuration) do |record, attr_name, value|
    unless cnpj_valid?(value)
      record.errors.add(attr_name)
    end
  end
end

def validates_cpf_format_of(*attr_names)
  configuration = DEFAULT_VALIDATION_OPTIONS.merge(attr_names.extract_options!)

  validates_each(attr_names, configuration) do |record, attr_name, value|
    unless cpf_format_valid?(value)
      record.errors.add(attr_name)
    end
  end
end

def validates_cnpj_format_of(*attr_names)
  configuration = DEFAULT_VALIDATION_OPTIONS.merge(attr_names.extract_options!)

  validates_each(attr_names, configuration) do |record, attr_name, value|
    unless cnpj_format_valid?(value)
      record.errors.add(attr_name)
    end
  end
end

#------------------------------------------------------------

def parse(string)
  string.gsub(/\D/, "").split(//).map(&:to_i) if string
end

def array_sum(array, multiplier, index, steps)
  return 0 if steps <= 0
  multiplier * array[index] + array_sum(array, multiplier-1, index+1, steps-1)
end

def cpf_valid?(cpf)
  values = parse(cpf)
  return false unless values
  return false unless values.length == 11
  return false if values.uniq.length == 1
  v1 = array_sum(values, 10, 0, 9)
  v1 = 11 - (v1 % 11)
  v1 = 0 if v1 >= 10
  return false if v1 != values[-2]
  v2 = array_sum(values, 11, 0, 9)
  v2 += 2 * v1
  v2 = 11 - (v2 % 11)
  v2 = 0 if v2 >= 10
  v2 == values[-1]
end

def cnpj_valid?(cnpj)
  values = parse(cnpj)
  return false unless values
  return false unless values.length == 14
  return false if values.uniq.length == 1
  v1 = array_sum(values, 5, 0, 4)
  v1 += array_sum(values, 9, 4, 8)
  v1 = 11 - (v1 % 11)
  v1 = 0 if v1 >= 10
  return false if v1 != values[-2]
  v2 = array_sum(values, 6, 0, 5)
  v2 += array_sum(values, 9, 5, 7)
  v2 += 2 * v1
  v2 = 11 - (v2 % 11)
  v2 = 0 if v2 >= 10
  v2 == values[-1]
end

def cpf_format_valid?(cpf)
  cpf =~ CPF_REGEX
end

def cnpj_format_valid?(cnpj)
  cnpj =~ CNPJ_REGEX
end

#------------------------------------------------------------

    end
  end
end
